package com.xxxtea.mpe.interceptor;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.AntPathMatcher;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.xxxtea.mpe.config.MpeConfig;
import com.xxxtea.mpe.helper.InfoHelper;
import com.xxxtea.mpe.helper.SwitchHelper;
import com.xxxtea.mpe.util.SqlPrintUtil;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.*;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.*;
import net.sf.jsqlparser.util.TablesNamesFinder;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.*;

/**
 * 联合表拦截器
 * 
 * MyBatis-Plus扩展拦截器，用于自动处理联合表查询，包括：
 * - 自动添加JOIN语句
 * - 扩展查询字段
 * - 处理WHERE条件
 * - SQL缓存优化
 * - 资源文件路径处理
 * 
 * 实现InnerInterceptor接口，在查询执行前拦截并修改SQL
 * 
 * @author badman
 * @since 1.0.0
 */
@Slf4j
@Component
public class UnionTableInterceptor implements InnerInterceptor {
	
	/**
	 * 资源文件路径拼接模板
	 * 用于处理JSON字段中的文件路径，格式：CONCAT('前缀', JSON_UNQUOTE(字段 -> '$.filename')) as 别名
	 */
	public final String CONCAT_TEMPLATE = "CONCAT('{}', JSON_UNQUOTE({} -> '$.filename')) as {}";
	
	/**
	 * 路径匹配器，用于匹配Mapper方法ID模式
	 */
	private final AntPathMatcher antPathMatcher = new AntPathMatcher();
	
	/**
	 * SQL缓存，避免重复解析相同的SQL
	 */
	private final Map<String, String> sqlCache = new HashMap<>();
	
	/**
	 * MPE配置对象
	 */
	@Resource
	private MpeConfig mpeConfig;
	
	/**
	 * 资源文件前缀，从配置文件中注入
	 */
	@Value("${mpe.union.resource-prefix}")
	private String resourcePrefix;

	/**
	 * 构造函数
	 * 记录拦截器启动日志
	 */
	public UnionTableInterceptor() {
		log.info("Start UnionTableInterceptor Successfully...");
	}

	/**
	 * 查询执行前的拦截方法
	 * 
	 * 在SQL执行前拦截，进行以下处理：
	 * - 检查是否为SELECT语句
	 * - 检查是否在忽略列表中
	 * - 检查开关状态
	 * - 处理联合表逻辑
	 * - 缓存优化
	 * - SQL日志记录
	 * 
	 * @param executor 执行器
	 * @param ms 映射语句
	 * @param parameter 参数对象
	 * @param rowBounds 行边界
	 * @param resultHandler 结果处理器
	 * @param boundSql 绑定SQL
	 */
	@Override
	public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
		// 获取联合表配置
		MpeConfig.UnionConfig unionConfig = mpeConfig.getUnion();
		
		// 只处理SELECT语句
		if (ms.getSqlCommandType() != SqlCommandType.SELECT) {
			return;
		}
		
		String mappedStatementId = ms.getId();
		List<String> ignoreMsPatterns = unionConfig.getIgnoreMsPatterns();
		
		// 检查是否在忽略列表中
		if (ignoreMsPatterns != null && ignoreMsPatterns.stream().anyMatch(e -> antPathMatcher.match(e, mappedStatementId))) {
			log.info("ignore ms: {}", mappedStatementId);
			return;
		}
		
		// 检查开关状态
		if (!SwitchHelper.isOpen(UnionTableInterceptor.class)) {
			log.info("SwitchHelper close, skip union! mappedStatementId: {}", mappedStatementId);
			return;
		}

		try {
			String newSql;
			String sql = boundSql.getSql();
			
			// 检查SQL缓存
			if (sqlCache.containsKey(sql)) {
				newSql = sqlCache.get(sql);
			} else {
				// 处理联合表逻辑
				newSql = handleUnionTable(boundSql);
				// 缓存处理后的SQL
				sqlCache.put(sql, newSql);
				log.info("cache mappedStatementId: {}", mappedStatementId);
			}
			
			// 替换原始SQL
			PluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql);
			mpBoundSql.sql(newSql);
			
			// 处理SQL日志记录
			if (!unionConfig.isLogSql()) {
				return;
			}
			if (unionConfig.isIgnoreUnchangedSqlLog() && sql.equals(newSql)) {
				return;
			}
			log.info("mappedStatementId: {}\n{}", mappedStatementId, SqlPrintUtil.format(sql, newSql, unionConfig.isPrettySql()));
		} catch (Exception e) {
			log.error("Union table intercept error", e);
		}
	}

	/**
	 * 处理联合表逻辑的核心方法
	 * 
	 * 解析SQL并添加联合表相关的JOIN、SELECT、WHERE等语句
	 * 
	 * @param boundSql 绑定SQL对象
	 * @return 处理后的SQL字符串
	 * @throws JSQLParserException SQL解析异常
	 */
	public String handleUnionTable(BoundSql boundSql) throws JSQLParserException {
		String sql = boundSql.getSql();
		Statement statement = CCJSqlParserUtil.parse(sql);
		
		// 只处理SELECT语句
		if (!(statement instanceof Select)) {
			return sql;
		}

		// 多表不进行处理
		Set<String> tableList = new TablesNamesFinder().getTables(statement);
		if (tableList.size() != 1) {
			return sql;
		}
		
		String tableName = CollUtil.getFirst(tableList);
		// 检查是否有联合表配置
		if (InfoHelper.getJoinInfo(tableName).isEmpty()) {
			return sql;
		}

		Select select = (Select) statement;
		PlainSelect plainSelect = select.getPlainSelect();

		// 如果包含函数，跳过处理
		if (plainSelect.getSelectItems().stream().anyMatch(e -> e.getExpression() instanceof Function)) {
			log.info("Sql selectItems contain function, skip union!");
			return sql;
		}

		// 获取主表信息
		Table mainTable = (Table) plainSelect.getFromItem();
		String mainTableAlias = mainTable.getAlias() != null ? mainTable.getAlias().getName() : mainTable.getName();
		
		// 创建JOIN语句
		List<Join> joins = new ArrayList<>();
		List<InfoHelper.JoinInfo> joinInfoList = InfoHelper.getJoinInfo(tableName);
		for (InfoHelper.JoinInfo joinInfo : joinInfoList) {
			Join join = createJoin(joinInfo, mainTableAlias);
			joins.add(join);
		}

		if (!joins.isEmpty()) {
			plainSelect.setJoins(joins);
		}

		// 处理WHERE条件
		handleAppendWhere(plainSelect, tableName, boundSql);
		// 处理SELECT字段
		handleSelectItems(plainSelect, tableName, mainTableAlias);
		// 处理WHERE字段
		handleWhereItems(plainSelect, tableName, mainTableAlias);
		
		return select.toString();
	}

	/**
	 * 处理追加的WHERE条件
	 * 
	 * 根据实体对象中的字段值自动添加WHERE条件
	 * 
	 * @param plainSelect 查询对象
	 * @param table 表名
	 * @param boundSql 绑定SQL
	 */
	private void handleAppendWhere(PlainSelect plainSelect, String table, BoundSql boundSql) {
		Object parameterObject = boundSql.getParameterObject();
		if (!(parameterObject instanceof Map)) {
			return;
		}
		MapperMethod.ParamMap<?> paramMap = (MapperMethod.ParamMap<?>) parameterObject;
		if (!paramMap.containsKey("ew")) {
			return;
		}

		// 检查是否包含LambdaQueryWrapper
		if (!paramMap.containsKey(Constants.WRAPPER) || !(paramMap.get(Constants.WRAPPER) instanceof LambdaQueryWrapper)) {
			return;
		}
		LambdaQueryWrapper<?> wrapper = (LambdaQueryWrapper<?>) paramMap.get(Constants.WRAPPER);
		Object entity = wrapper.getEntity();
		if (entity == null) {
			return;
		}

		// 根据扩展信息添加WHERE条件
		for (InfoHelper.ExtendInfo extendInfo : InfoHelper.getExtendInfo(table)) {
			String tableName = extendInfo.getReferenceTable();
			if (!extendInfo.getTableAlias().isEmpty()) {
				tableName = extendInfo.getTableAlias();
			}
			String columnName = tableName + "." + extendInfo.getTargetColumn();
			Object fieldValue = ReflectUtil.getFieldValue(entity, extendInfo.getFieldName());
			if (fieldValue == null) {
				continue;
			}

			// 创建等值条件
			Expression newCondition = new EqualsTo(new Column(columnName), new StringValue(fieldValue.toString()));
			Expression where = plainSelect.getWhere();
			if (where == null) {
				plainSelect.setWhere(newCondition);
			} else {
				// 使用AND连接多个条件
				plainSelect.setWhere(new AndExpression(where, newCondition));
			}
		}
	}

	/**
	 * 创建JOIN语句
	 * 
	 * 根据连接信息创建不同类型的JOIN语句
	 * 
	 * @param joinInfo 连接信息
	 * @param mainTableAlias 主表别名
	 * @return JOIN对象
	 */
	private Join createJoin(InfoHelper.JoinInfo joinInfo, String mainTableAlias) {
		Join join = new Join();
		
		// 设置JOIN类型
		switch (joinInfo.getJoinType()) {
			case 1:
				join.setLeft(true);  // LEFT JOIN
				break;
			case 2:
				join.setRight(true); // RIGHT JOIN
				break;
			case 3:
				join.setInner(true); // INNER JOIN
				break;
		}

		// 设置右表
		Table rightTable = new Table(joinInfo.getTableName());
		if (!joinInfo.getTableAlias().isEmpty()) {
			rightTable.setAlias(new Alias(joinInfo.getTableAlias()));
		}
		join.setRightItem(rightTable);

		// 设置连接条件
		Column leftColumn = new Column(mainTableAlias + "." + joinInfo.getColumn());
		Column rightColumn = new Column(joinInfo.getTableName() + "." + joinInfo.getReferenceColumn());
		if (!joinInfo.getTableAlias().isEmpty()) {
			rightColumn.setColumnName(joinInfo.getTableAlias() + "." + joinInfo.getReferenceColumn());
		}
		join.setOnExpressions(Collections.singletonList(new EqualsTo(leftColumn, rightColumn)));
		return join;
	}

	/**
	 * 处理SELECT字段
	 * 
	 * 为原有字段添加表别名，并添加扩展查询字段
	 * 
	 * @param plainSelect 查询对象
	 * @param tableName 表名
	 * @param mainTableAlias 主表别名
	 */
	private void handleSelectItems(PlainSelect plainSelect, String tableName, String mainTableAlias) {
		List<SelectItem<?>> selectItems = new ArrayList<>();
		
		// 处理原有的Select项，添加表别名
		for (SelectItem<?> item : plainSelect.getSelectItems()) {
			Expression expression = item.getExpression();
			if (expression instanceof Column) {
				selectItems.add(new SelectItem<>(new Column(new Table(mainTableAlias), item.toString())));
			} else if (item.getExpression() instanceof AllColumns) {
				selectItems.add(item);
			}
		}

		// 添加扩展查询字段
		List<InfoHelper.ExtendInfo> extendInfoList = InfoHelper.getExtendInfo(tableName);
		for (InfoHelper.ExtendInfo extendInfo : extendInfoList) {
			SelectItem<?> item = createExtendQueryItem(extendInfo);
			selectItems.add(item);
		}

		plainSelect.setSelectItems(selectItems);
	}

	/**
	 * 处理WHERE字段
	 * 
	 * 为WHERE条件中的字段添加表别名
	 * 
	 * @param plainSelect 查询对象
	 * @param tableName 表名
	 * @param mainTableAlias 主表别名
	 */
	private void handleWhereItems(PlainSelect plainSelect, String tableName, String mainTableAlias) {
		Expression where = plainSelect.getWhere();
		if (where == null) {
			return;
		}
		TableInfo tableInfo = TableInfoHelper.getTableInfo(tableName);
		where.accept(new ExpressionVisitorAdapter() {
			@Override
			public void visit(Column column) {
				// 如果where语句中的字段属于该表，添加表别名
				if (tableInfo.getAllSqlSelect().contains(column.getColumnName())) {
					column.setTable(new Table(mainTableAlias));
				}
			}
		});
	}

	/**
	 * 创建扩展查询字段
	 * 
	 * 根据扩展信息创建SELECT字段，支持资源文件路径处理
	 * 
	 * @param extendInfo 扩展信息
	 * @return SELECT字段对象
	 */
	private SelectItem<?> createExtendQueryItem(InfoHelper.ExtendInfo extendInfo) {
		String tableName = extendInfo.getReferenceTable();
		if (!extendInfo.getTableAlias().isEmpty()) {
			tableName = extendInfo.getTableAlias();
		}

		// 如果启用资源处理，使用CONCAT模板
		if (extendInfo.isEnableResource()) {
			String columnName = tableName + "." + extendInfo.getTargetColumn();
			String column = StrUtil.format(CONCAT_TEMPLATE, resourcePrefix, columnName, extendInfo.getFieldName());
			return new SelectItem<>(new Column(column));
		}

		// 普通字段处理
		SelectItem<?> selectExpressionItem = new SelectItem<>(new Column(new Table(tableName), extendInfo.getTargetColumn()));
		selectExpressionItem.setAlias(new Alias(extendInfo.getFieldName()));
		return selectExpressionItem;
	}
}